﻿/*
 * Copyright 2009 - 2010 eScience Institute
 *
 * This file is part of SqlShare.
 *
 * SqlShare is free software: you can redistribute it and/or modify it under the terms 
 * of the GNU General Public License as published by the Free Software Foundation, 
 * either version 3 of the License, or (at your option) any later version.
 *
 * SqlShare is distributed in the hope that it will be useful, but WITHOUT ANY 
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with 
 * SqlShare. If not, see http://www.gnu.org/licenses/.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;

namespace RawData
{
    public class DataFileReader
    {
        private StreamReader _reader;
        private long _fileSize;
        private bool _hasColumnHeader;
        bool isComplete;
        private SqlTableMetadata _dataTable;

        // determine this when we read the file CheckFile()
        private string _literalFieldDelimiter;
        private string _literalRowDelimiter;        

        public DataFileReader(string tablename, char delimiter, bool hasHeader)
        {            
            _dataTable = new SqlTableMetadata(tablename, delimiter);    // todo, allow user to change
            _hasColumnHeader = hasHeader;
            isComplete = false;
        }

        /// <summary>
        /// Clean the line that is read from the datafile
        /// </summary>
        /// <param name="line"></param>
        /// <param name="peel"></param>
        /// <returns></returns>
        private string CleanLine(string line, bool peel)
        {
            if (peel && line != null)
            {
                int lastIndex = line.Length - 1;
                if (lastIndex > 0)
                {
                    line = line.Substring(0, lastIndex);
                }
            }
            return line;
        }

        private void _ExtractMetadata(object o)
        {
            object[] oa = (object[])o;
            Stream stream = (Stream)oa[0];
            bool async = (bool)oa[1];
            try
            {
                _fileSize = stream.Length;
                _reader = new StreamReader(stream);

                // check file type if unix (important for desnigning bulk insert statement
                CheckFileType();

                bool peelLastFieldTerminator = _literalRowDelimiter.Contains(_literalFieldDelimiter);

                // open a reader for this file            
                _reader.BaseStream.Position = 0;
                string line = CleanLine(_reader.ReadLine(), peelLastFieldTerminator);


                // read header (has column names)
                if (line != null)
                {
                    _dataTable.ParseHeader(line, _hasColumnHeader);
                    // only move to the next line if we are skipping this line of non-data
                    if (!_hasColumnHeader)
                    {
                        line = CleanLine(_reader.ReadLine(), peelLastFieldTerminator);
                    }
                }


                while (line != null)
                {
                    _dataTable.ParseData(line);
                    line = CleanLine(_reader.ReadLine(), peelLastFieldTerminator);
                }

                isComplete = true;
            }
            catch (Exception e)
            {
                if (async)
                {
                    AsyncException = e;
                }
                else
                {
                    throw e;
                }
            }
        }

        public Exception AsyncException { get; private set; }

        /// <summary>
        /// Start a thread that will extract metadata. 
        /// </summary>
        public void ExtractMetadata(Stream datastream, bool async)
        {
            if (async)
            {
                Thread t = new Thread(new ParameterizedThreadStart(_ExtractMetadata));
                t.Start(new object[]{datastream, async});
            }
            else
            {
                _ExtractMetadata(new object[] {datastream, async});
            }
        }

        public SqlTableMetadata SQLTable
        {
            get
            {
                return _dataTable;
            }
        }               
         
        /// <summary>
        /// Get the percent of this operation
        /// </summary>
        public double PercentComplete
        {
            get
            {
                if (isComplete)
                {
                    return 1;
                }

                if (AsyncException != null)
                {
                    return -1;
                }

                if (_reader != null && _reader.BaseStream != null && _reader.BaseStream.CanSeek)
                {
                    double val = ((double)_reader.BaseStream.Position / (double)_fileSize);
                    if (val >= 1)
                    {
                        return 0.99D;
                    }
                    return val;
                }

                return 0;
            }
        }

        /// <summary>
        /// Check to see what filetype this data file is. (Important when doing bulk inserts)
        /// </summary>
        private void CheckFileType()
        {
            _reader.BaseStream.Position = 0;
            StreamReader reader = new StreamReader(_reader.BaseStream);            
            char[] block = new char[2000];
            reader.ReadBlock(block, 0, 2000);
            string blockString = new string(block);
            

            //  (\n)  * LF:    Multics, Unix and Unix-like systems (GNU/Linux, AIX, Xenix, Mac OS X, FreeBSD, etc.), BeOS, Amiga, RISC OS, and others
            //  (\r\n)* CR+LF: DEC RT-11 and most other early non-Unix, non-IBM OSes, CP/M, MP/M, DOS, OS/2, Microsoft Windows, Symbian OS
            //  (\r)  * CR:    Commodore 8-bit machines, Apple II family, Mac OS up to version 9 and OS-9
            
            string LF = "\n";   //new string(new char[]{(char)10});
            string CR = "\r";

            int countLF = countIterations(LF, blockString);
            int countCR = countIterations(CR, blockString);

            // WINDOWS FORMAT
            string formatNewline;
            if (countLF == countCR)
            {
                _literalRowDelimiter = @"\n";
                formatNewline = CR + LF;
            }
            // UNIX FORMAT
            else if (countLF > countCR)
            {
                _literalRowDelimiter = @"' + nchar(10) + '";
                formatNewline = LF;
            }
            // MAC OS FORMAT
            else
            {
                _literalRowDelimiter = @"\r";
                formatNewline = CR;
            }

            _literalFieldDelimiter = (_dataTable.ColumnDelimiter == '\t')? @"\t" : new string(new char[]{_dataTable.ColumnDelimiter});

            // check to see if we have our field delimiter as excess on the end of our newline
            int countFD = countIterations(_dataTable.ColumnDelimiter + formatNewline, blockString);
            if ((countFD == countLF && countLF > countCR) || (countFD == countCR && countCR > countLF))
            {
                _literalRowDelimiter = _literalFieldDelimiter + _literalRowDelimiter;
            }                       
        }

        /// <summary>
        /// Count the number of times the key is found in the phrase
        /// </summary>
        /// <param name="key"></param>
        /// <param name="phrase"></param>
        /// <returns></returns>
        private static int countIterations(string key, string phrase)
        {
            int c = 0;
            for (int i = 0; (i = phrase.IndexOf(key, i) + 1) > 0; c++) ;
            return c;
        }
    }   
}
